ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonTest / kotlin / org / meshtastic / core / data / manager / NodeManagerImplTest.kt
Displaying Raw β’ Download
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 108.43 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager
Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.calls
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.everySuspend
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.matcher.capture.capture
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787dev.mokkery.verify
Tff7b72import T7ee787dev.mokkery.verify.VerifyMode
Tff7b72import T7ee787dev.mokkery.verify.VerifyMode.Companion.exactly
Tff7b72import T7ee787dev.mokkery.verifySuspend
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineStart
Tff7b72import T7ee787kotlinx.coroutines.async
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.test.TestScope
Tff7b72import T7ee787kotlinx.coroutines.test.advanceUntilIdle
Tff7b72import T7ee787kotlinx.coroutines.test.runCurrent
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.common.util.crc32
Tff7b72import T7ee787org.meshtastic.core.model.MyNodeInfo
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.Notification
Tff7b72import T7ee787org.meshtastic.core.repository.NotificationManager
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionContext
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetadata
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetrics
Tff7b72import T7ee787org.meshtastic.proto.EnvironmentMetrics
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.Paxcount
Tff7b72import T7ee787org.meshtastic.proto.StatusMessage
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787org.meshtastic.proto.NodeInfo Tff7b72as Te6edf3ProtoNodeInfo
Tff7b72import T7ee787org.meshtastic.proto.Position Tff7b72as Te6edf3ProtoPosition
Tff7b72class T56d364NodeManagerImplTest Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3notificationManagerTb4b4b4: Te6edf3NotificationManager Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceService Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3testScope Tff7b72= Te6edf3TestScopeTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3nodeManagerTb4b4b4: Te6edf3NodeManagerImpl
Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetUpTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3nodeManager Tff7b72= Te6edf3NodeManagerImplTb4b4b4(Te6edf3nodeRepositoryTb4b4b4, Te6edf3notificationManagerTb4b4b4, Te6edf3radioInterfaceServiceTb4b4b4, Te6edf3testScopeTb4b4b4)
T8b949e// Override the compose-resources formatter so notification dispatch is deterministic in the
T8b949e// plain-JVM test env (getStringSuspend does not resolve here). Tests that assert "no dispatch"
T8b949e// still hold: the override only changes the title, not whether dispatch fires.
Te6edf3nodeManagerTb4b4b4.Te6edf3notificationTitleFormatter Tff7b72= Tb4b4b4{ Te6edf3shortName Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ffNew node seen: Tffd700$Te6edf3shortNameTa5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- In-memory index bounding ----------
T8b949e/** Drives [NodeManagerImpl.updateNode], the growth path for the in-memory index, with distinct node numbers. */
Tff7b72private Tff7b72fun Td2a8fffloodDistinctNodesTb4b4b4(Te6edf3countTb4b4b4: Tffa657IntTb4b4b4, Te6edf3startAtTb4b4b4: Tffa657Int Tff7b72= T79c0ff1T79c0ff0T79c0ff0Te6edf3_000Tb4b4b4) Tb4b4b4{
Te6edf3repeatTb4b4b4(Te6edf3countTb4b4b4) Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72> Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3startAt Tff7b72+ Te6edf3iTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= Te6edf3iTb4b4b4) Tb4b4b4} Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Floods past the cap and asserts eviction actually ran.
*
* The protection tests below must assert this too: without it they pass trivially when eviction is disabled, since
* nothing being evicted also means the protected node survives.
*/
Tff7b72private Tff7b72fun Td2a8fffloodPastCapAndAssertEvictedTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3floodDistinctNodesTb4b4b4(Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODES Tff7b72+ T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3size Tff7b72<Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODESTb4b4b4,
Ta5d6ff"Ta5d6ffeviction did not run: index is Tffd700${Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`a flood of novel node numbers cannot grow the in-memory index without bound`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// packet.from is unauthenticated, so every novel value would otherwise allocate a permanent Node. The index is
T8b949e// read only inside core:data for correlation, and every consumer tolerates a miss, so eviction is safe here.
Te6edf3floodDistinctNodesTb4b4b4(Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODES Tff7b72+ T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3size Tff7b72<Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODESTb4b4b4,
Ta5d6ff"Ta5d6ffindex grew to Tffd700${Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff, over the Tffd700${Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODESTffd700}Ta5d6ff capTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`a flood of novel NodeInfo packets cannot grow the in-memory index without bound`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// handleReceivedUser commits its own index rather than going through updateNodeState, so bounding only the
T8b949e// latter left this path β the one an unauthenticated peer drives most directly β completely unbounded.
Te6edf3repeatTb4b4b4(Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODES Tff7b72+ T79c0ff5T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Tff7b72val Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff0Te6edf3_000 Tff7b72+ Te6edf3i
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3numTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffFlood Tffd700$Te6edf3iTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffFTffd700$Te6edf3iTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3assertTrueTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3size Tff7b72<Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3MAX_IN_MEMORY_NODESTb4b4b4,
Ta5d6ff"Ta5d6ffindex grew to Tffd700${Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff via the NodeInfo pathTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`eviction never drops the local node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Deliberately left as a bare placeholder with the oldest lastHeard, i.e. the FIRST node eviction would
T8b949e// otherwise pick. A local node with real NodeInfo survives incidentally by sorting last, which would not
T8b949e// exercise the explicit exemption at all.
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff4T79c0ff2T79c0ff4T79c0ff2
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3myNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= Tff7b72-T79c0ff1Tb4b4b4) Tb4b4b4}
Te6edf3floodPastCapAndAssertEvictedTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3myNumTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffthe local node must never be evictedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`eviction never drops user-marked nodes`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3favourite Tff7b72= T79c0ff5T79c0ff1T79c0ff5T79c0ff0
Tff7b72val Te6edf3ignored Tff7b72= T79c0ff5T79c0ff1T79c0ff5T79c0ff1
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3favouriteTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3isFavorite Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3ignoredTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3isIgnored Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Te6edf3floodPastCapAndAssertEvictedTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3favouriteTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffa favourite must never be evictedTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3ignoredTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffan ignored node must never be evictedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`eviction prefers placeholders over nodes with a real identity`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3identified Tff7b72= T79c0ff7T79c0ff7T79c0ff7T79c0ff7
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3identifiedTb4b4b4) Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffReal NodeTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3floodPastCapAndAssertEvictedTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3identifiedTff7b72]Tb4b4b4,
Ta5d6ff"Ta5d6ffa node with a real NodeInfo identity should outlive bare-packet placeholdersTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getOrCreateNode creates default user for unknown node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getOrCreateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3numTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ffMeshtasticTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`updateNodeAndPersist awaits the repository write`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Tff7b72val Te6edf3writeStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseWrite Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3writeStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseWriteTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3update Tff7b72= Te6edf3async Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3writeStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3updateTb4b4b4.Te6edf3isCompletedTb4b4b4)
Te6edf3releaseWriteTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3updateTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`same-node persistence cannot finish with an older snapshot`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Tff7b72val Te6edf3firstWriteStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3secondWriteStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseFirstWrite Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3persisted Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NodeTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3invocation Tff7b72= T79c0ff0
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Tffa657itTb4b4b4.Te6edf3argTff7b72<Te6edf3NodeTff7b72>Tb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3invocationTff7b72+Tff7b72+
Tff7b72when Tb4b4b4(Te6edf3invocationTb4b4b4) Tb4b4b4{
T79c0ff1 Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3firstWriteStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseFirstWriteTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}
T79c0ff2 Tff7b72-Tff7b72> Te6edf3secondWriteStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Te6edf3persisted Tff7b72+Tff7b72= Te6edf3node
Tb4b4b4}
Tff7b72val Te6edf3first Tff7b72= Te6edf3async Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1Tb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3firstWriteStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3second Tff7b72=
Te6edf3asyncTb4b4b4(Te6edf3start Tff7b72= Te6edf3CoroutineStartTb4b4b4.Te6edf3UNDISPATCHEDTb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff2Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(
Te6edf3secondWriteStartedTb4b4b4.Te6edf3isCompletedTb4b4b4,
Ta5d6ff"Ta5d6ffthe second upsert must not enter while the first owns its laneTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3releaseFirstWriteTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3firstTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3secondTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3persistedTb4b4b4.Te6edf3mapTb4b4b4(Te6edf3NodeTff7b72::Te6edf3lastHeardTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`session-bound node persistence from a retired generation is rejected`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3oldSession Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff7LTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffble:sameTa5d6ff"Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3oldSessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeForSessionTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3oldSessionTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4}
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`node persistence is suppressed while database writes are disabled`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72falseTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser preserves existing user if incoming is default`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffMCNTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)
T8b949e// Setup existing node
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3incomingDefaultUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic 5678Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ff5678Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingDefaultUserTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_modelTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser updates user if incoming is higher detail`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
T8b949e// Use a non-UNSET hw_model so isUnknownUser=false (avoids new-node notification + getString)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffOld NameTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffONTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3incomingDetailedUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffReal UserTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRUTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V1Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingDetailedUserTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffReal UserTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V1Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_modelTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition updates node position`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3position Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3positionTb4b4b4, T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition with zero coordinates preserves last known location but updates satellites`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3initialPosition Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff1T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3initialPositionTb4b4b4, T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4)
T8b949e// Receive "zero" position with new satellite count
Tff7b72val Te6edf3zeroPosition Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff5Tb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3nodeNumTb4b4b4, T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3zeroPositionTb4b4b4, T79c0ff1T79c0ff0T79c0ff0T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3sats_in_viewTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition keeps precise position when a coarse report lands in the same box`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Regression test for #6360: a fixed position set from the app is exact, but the node then broadcasts
T8b949e// that same position coarsened to the channel's position_precision. The coarse report must not
T8b949e// replace the exact coordinates we already hold, otherwise the value shown in the app "drifts".
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3exactLatI Tff7b72= T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0 T8b949e// 52.4595
Tff7b72val Te6edf3exactLonI Tff7b72= T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0 T8b949e// 31.0255
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= Te6edf3exactLatITb4b4b4, Te6edf3longitude_i Tff7b72= Te6edf3exactLonITb4b4b4, Te6edf3altitude Tff7b72= T79c0ff1T79c0ff3T79c0ff5Tb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4,
T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
T8b949e// Firmware coarsening for precision_bits = 13: mask off the low (32 - 13) bits, then re-center the box.
Tff7b72val Te6edf3coarseLatI Tff7b72= Tb4b4b4(Te6edf3exactLatI Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4) T8b949e// 524550144 -> 52.4550144
Tff7b72val Te6edf3coarseLonI Tff7b72= Tb4b4b4(Te6edf3exactLonI Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4) T8b949e// 310116352 -> 31.0116352
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(
Te6edf3latitude_i Tff7b72= Te6edf3coarseLatITb4b4b4,
Te6edf3longitude_i Tff7b72= Te6edf3coarseLonITb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff2T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3sats_in_view Tff7b72= T79c0ff7Tb4b4b4,
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3precision_bits Tff7b72= T79c0ff1T79c0ff3Tb4b4b4,
Tb4b4b4)Tb4b4b4,
T79c0ff2T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3exactLatITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3exactLonITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
T8b949e// The stored precision must be retained as well. If the incoming precision_bits were kept, the *next*
T8b949e// coarse report would compare as equally precise and would be free to overwrite the exact coordinates.
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3precision_bitsTb4b4b4)
T8b949e// Everything else from the newer report is still applied.
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff1T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3altitudeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3sats_in_viewTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3timeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition accepts a coarse report from a different box`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4,
T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
T8b949e// A coarse report whose box does not contain the stored position means the node actually moved.
Tff7b72val Te6edf3movedLatI Tff7b72= Tb4b4b4(Tb4b4b4(T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff2T79c0ff0Tb4b4b4)Tb4b4b4) Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)
Tff7b72val Te6edf3movedLonI Tff7b72= Tb4b4b4(Tb4b4b4(T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff2T79c0ff0Tb4b4b4)Tb4b4b4) Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= Te6edf3movedLatITb4b4b4, Te6edf3longitude_i Tff7b72= Te6edf3movedLonITb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3precision_bits Tff7b72= T79c0ff1T79c0ff3Tb4b4b4)Tb4b4b4,
T79c0ff2T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3movedLatITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3movedLonITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff3Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3precision_bitsTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition accepts a coarse report when the stored position is coarser`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3coarseLatI Tff7b72= Tb4b4b4(T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff2T79c0ff1Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff2T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3coarseLonI Tff7b72= Tb4b4b4(T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff2T79c0ff1Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff2T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= Te6edf3coarseLatITb4b4b4, Te6edf3longitude_i Tff7b72= Te6edf3coarseLonITb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3precision_bits Tff7b72= T79c0ff1T79c0ff1Tb4b4b4)Tb4b4b4,
T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3finerLatI Tff7b72= Tb4b4b4(T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)
Tff7b72val Te6edf3finerLonI Tff7b72= Tb4b4b4(T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0 Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= Te6edf3finerLatITb4b4b4, Te6edf3longitude_i Tff7b72= Te6edf3finerLonITb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3precision_bits Tff7b72= T79c0ff1T79c0ff3Tb4b4b4)Tb4b4b4,
T79c0ff2T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3finerLatITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3finerLonITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff3Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3precision_bitsTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedPosition for local node ignores purely empty packets`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff1T79c0ff1T79c0ff1
Tff7b72val Te6edf3emptyPos Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff0Tb4b4b4, Te6edf3sats_in_view Tff7b72= T79c0ff0Tb4b4b4, Te6edf3time Tff7b72= T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3myNumTb4b4b4, Te6edf3emptyPosTb4b4b4, T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3myNumTff7b72]
T8b949e// Should still be null since the empty position for local node is ignored
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates lastHeard`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates device metrics`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff7T79c0ff5Tb4b4b4, Te6edf3voltage Tff7b72= T79c0ff3.8fTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3deviceMetricsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7T79c0ff5Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_levelTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3.8fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3voltageTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry updates environment metrics`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3telemetry Tff7b72=
Te6edf3TelemetryTb4b4b4(Te6edf3environment_metrics Tff7b72= Te6edf3EnvironmentMetricsTb4b4b4(Te6edf3temperature Tff7b72= T79c0ff2T79c0ff2.5fTb4b4b4, Te6edf3relative_humidity Tff7b72= T79c0ff4T79c0ff5.0fTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3environmentMetricsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff2.5fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3temperatureTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5.0fTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3relative_humidityTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`clear resets internal state`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTestTa5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!000004d2Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3valueTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns broadcast ID for broadcast nodeNum`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3NODENUM_BROADCASTTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns default hex ID for unknown node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(T79c0ff0Te6edf3x1234Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(T79c0ff0Te6edf3x1234Tb4b4b4)Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`toNodeID returns user ID for known node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!customidTa5d6ff"
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(Te6edf3nodeNumTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3userIdTb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`removeByNodenum removes node from map`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTestTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffTTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3assertTrueTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3containsKeyTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3removeByNodenumTb4b4b4(Te6edf3nodeNumTb4b4b4)
Te6edf3assertTrueTb4b4b4(Tff7b72!Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3containsKeyTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!testnodeTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser sets publicKey from user public_key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffExistingTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffEXTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffUPTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingUserTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasPKCTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedUser sets empty publicKey when key mismatch clears user key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3existingPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffExistingTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffEXTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3existingPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3existingUserTb4b4b4, Te6edf3publicKey Tff7b72= Te6edf3existingPkTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3differentPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!12345678Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffUPTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3differentPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3incomingUserTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
T8b949e// Key mismatch: newUser gets public_key cleared to EMPTY, and publicKey should match
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`installNodeInfo sets publicKey from user public_key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!abcd1234Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote NodeTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRNTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3info Tff7b72= Te6edf3ProtoNodeInfoTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3userTb4b4b4, Te6edf3last_heard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3pkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasPKCTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`installNodeInfo keeps precise position when the snapshot only has the coarse one`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3exactLatI Tff7b72= T79c0ff5T79c0ff2T79c0ff4T79c0ff5T79c0ff9T79c0ff5T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3exactLonI Tff7b72= T79c0ff3T79c0ff1T79c0ff0T79c0ff2T79c0ff5T79c0ff5T79c0ff0T79c0ff0T79c0ff0
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3nodeNumTb4b4b4,
T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= Te6edf3exactLatITb4b4b4, Te6edf3longitude_i Tff7b72= Te6edf3exactLonITb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4,
T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3coarse Tff7b72=
Te6edf3ProtoPositionTb4b4b4(
Te6edf3latitude_i Tff7b72= Tb4b4b4(Te6edf3exactLatI Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)Tb4b4b4,
Te6edf3longitude_i Tff7b72= Tb4b4b4(Te6edf3exactLonI Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl T79c0ff1T79c0ff9Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl T79c0ff1T79c0ff8Tb4b4b4)Tb4b4b4,
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3precision_bits Tff7b72= T79c0ff1T79c0ff3Tb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3ProtoNodeInfoTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3position Tff7b72= Te6edf3coarseTb4b4b4, Te6edf3last_heard Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3exactLatITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3exactLonITb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
T8b949e// Full precision is retained, so a later coarse snapshot cannot displace the exact coordinates either.
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3precision_bitsTb4b4b4)
T8b949e// The snapshot's newer timestamps are still adopted.
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3timeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`installNodeInfo clears publicKey for licensed users`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
Tff7b72val Te6edf3pk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!abcd1234Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLicensed OpTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLOTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Te6edf3is_licensed Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3info Tff7b72= Te6edf3ProtoNodeInfoTb4b4b4(Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3user Tff7b72= Te6edf3userTb4b4b4, Te6edf3last_heard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72!!
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo returns null when repository has no info`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo synthesizes from repository and nodeDB`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3repoInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3myNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Ta5d6ff"Ta5d6fftbeamTa5d6ff"Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff1T79c0ff0T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3repoInfoTb4b4b4)
T8b949e// Add node with position (non-zero lat β hasGPS = true)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3myNumTb4b4b4, Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!mydeviceTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TBEAMTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3myNumTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3myNodeNumTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3hasGPSTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fftbeamTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3modelTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff!mydeviceTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3deviceIdTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getMyNodeInfo falls back to nodeDB model when repository model is null`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3repoInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3myNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff1T79c0ff0T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3repoInfoTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3HELTEC_V3Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffHELTEC_V3Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3modelTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedTelemetry with null metrics does not crash`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4}
T8b949e// Telemetry with no metrics at all
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3telemetryTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getMyId returns empty when disconnected`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`getMyId returns user ID when connected`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3myNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3myNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!mynode42Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff!mynode42Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4)
Tb4b4b4}
T8b949e// ---------- Atomic identity reconciliation ----------
T8b949e//
T8b949e// Covers the CAS-loop reducer that replaced the legacy read-then-mutate sequence. Each test asserts typed
T8b949e// in-memory state and the permitted side effects (ordinary same-number upsert and notification dispatch).
Tff7b72private Tff7b72val Te6edf3validPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72fun Te6edf3ByteStringTb4b4b4.Td2a8ffcanonicalNumTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Te6edf3crc32Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72fun Te6edf3ByteStringTb4b4b4.Td2a8ffnoncanonicalNumTb4b4b4(Te6edf3preferredTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Int Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3preferred Tff7b72!Tff7b72= Te6edf3canonicalNumTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3preferred Tff7b72else Te6edf3preferred Tff7b72+ T79c0ff1
Tff7b72private Tff7b72fun Td2a8ffmakeKnownNodeTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3pkTb4b4b4: Te6edf3ByteStringTb4b4b4, Te6edf3nameTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ffKnownTa5d6ff"Tb4b4b4)Tb4b4b4: Te6edf3Node Tff7b72= Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3numTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!Tffd700${Te6edf3numTb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Te6edf3nameTb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3nameTb4b4b4.Te6edf3takeTb4b4b4(T79c0ff3Tb4b4b4)Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3pkTb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffenableDbWritesTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffverifyNoRepositoryDeletionTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3deleteNodeTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3deleteNodesTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- Trusted migration retirement ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`retired number rejects delayed field sequence without persistence`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3retiredNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff4T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3retiredNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPre-migrationTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3retiredNumTb4b4b4)Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3TelemetryTb4b4b4(Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3myNodeNum Tff7b72= T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3p Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4)Tb4b4b4,
Te6edf3defaultTime Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPaxcounterTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3PaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff5Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedNodeStatusTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3StatusMessageTb4b4b4(Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffstaleTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(
Te6edf3ProtoNodeInfoTb4b4b4(
Te6edf3num Tff7b72= Te6edf3retiredNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!retiredTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRetiredTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRETTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3insertMetadataTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3DeviceMetadataTb4b4b4(Te6edf3firmware_version Tff7b72= Ta5d6ff"Ta5d6ff2.7.0Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3insertMetadataTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`retired number suppresses keyless and represented-key user replays`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3retiredNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff4T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3retiredNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffRetiredTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3retiredNumTb4b4b4)Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!keylessTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffKeyless replayTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffKEYTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!invalidTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffInvalid-key replayTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffINVTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3byteArrayOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4, T79c0ff3Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!representedTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRepresented replayTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffREPTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`retired number accepts validated new-key reuse and un-retires the slot`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Seed a trusted old identity at the number FIRST, so the migration retires it with a trusted key hint. Without
T8b949e// the hint, a later key packet would be suppressed as RETIRED_WITHOUT_HINT_SUPPRESSED and the slot would stay
T8b949e// retired for the session (see reduceReceivedUser).
Tff7b72val Te6edf3retiredNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff4T79c0ff3T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3replacementKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff8T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3retiredNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3retiredNumTb4b4b4)Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!replacementTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffReplacementTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffNEWTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3replacementKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3replacement Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3replacementTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3replacementKeyTb4b4b4, Te6edf3replacementTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffReplacementTa5d6ff"Tb4b4b4, Te6edf3replacementTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`loadCachedNodeDB preserves retirement and suppresses reloaded stale replay`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// The repo still holds the retired node's row; a cache reload must keep the trusted retirement and must NOT
T8b949e// resurrect the retired number into the live index, and a later stale replay at that number stays suppressed.
Tff7b72val Te6edf3retiredNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff4T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3retiredNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffRetiredTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3mapOfTb4b4b4(Te6edf3retiredNum Te6edf3to Te6edf3retiredNodeTb4b4b4)Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3mapOfTb4b4b4(Te6edf3retiredNum Te6edf3to Te6edf3retiredNodeTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3retiredNumTb4b4b4)Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Retirement survived the reload AND the reloaded row was filtered out of the live index.
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tb4b4b4)
T8b949e// A stale keyless replay at the retired number is still suppressed after the reload.
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3retiredNumTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!staleTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStale replayTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`clear starts a new session without retired-number suppression`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3retiredNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff4T79c0ff4T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3retiredNumTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3TelemetryTb4b4b4(Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff1T79c0ff0Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedTelemetryTb4b4b4(Te6edf3retiredNumTb4b4b4, Te6edf3TelemetryTb4b4b4(Te6edf3device_metrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff9T79c0ff0Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff0Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3retiredNumTff7b72]Tff7b72?.Te6edf3deviceMetricsTff7b72?.Te6edf3battery_levelTb4b4b4)
Tb4b4b4}
T8b949e// 1. Established same-key duplicate
Tf0883e@Test
Tff7b72fun Td2a8ff`established same-key stale replay is removed in memory without repository mutation`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3newNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3newNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3newNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffMigratedTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3oldNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffStale EstablishedTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!oldTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3staleUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3canonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3newNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3canonicalTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMigratedTa5d6ff"Tb4b4b4, Te6edf3canonicalTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validPkTb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 2. Placeholder duplicate
Tf0883e@Test
Tff7b72fun Td2a8ff`stale replay preserves no-key telemetry placeholder and canonical identity`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3newNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3newNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3newNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffMigratedTa5d6ff"Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3placeholderKey Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTY
Tff7b72val Te6edf3defaultId Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3oldNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3oldNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3oldNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3defaultIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic Tffd700${Te6edf3defaultIdTb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3defaultIdTb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3placeholderKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3placeholderKeyTb4b4b4,
Te6edf3position Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4)Tb4b4b4,
Te6edf3channel Tff7b72= T79c0ff7Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!oldTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3staleUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3placeholder Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3placeholderTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3defaultIdTb4b4b4, Te6edf3placeholderTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3placeholderTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3placeholderTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7Tb4b4b4, Te6edf3placeholderTb4b4b4.Te6edf3channelTb4b4b4)
Tff7b72val Te6edf3canonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3newNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3canonicalTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMigratedTa5d6ff"Tb4b4b4, Te6edf3canonicalTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 3. Different-key conflict
Tf0883e@Test
Tff7b72fun Td2a8ff`different-key conflict preserves both nodes and emits no side effects`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3fromNum Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3otherNum Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3differentPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3otherNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3otherNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOtherTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3fromNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3differentPkTb4b4b4, Ta5d6ff"Ta5d6ffEstablishedTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!staleTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3incomingUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fromNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3fromNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3fromNodeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffEstablishedTa5d6ff"Tb4b4b4, Te6edf3fromNodeTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3differentPkTb4b4b4, Te6edf3fromNodeTb4b4b4.Te6edf3publicKeyTb4b4b4)
Tff7b72val Te6edf3otherNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3otherNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3otherNodeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffOtherTa5d6ff"Tb4b4b4, Te6edf3otherNodeTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 4. Multiple same-key matches
Tf0883e@Test
Tff7b72fun Td2a8ff`ambiguous noncanonical duplicates update from entry and preserve every candidate`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeA Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3nodeB Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3fromNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeATb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3nodeATb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffAlphaTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeBTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3nodeBTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffBravoTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3fromNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffBeforeTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3incomingUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!incomingTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffIncomingTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffINCTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3incomingUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// No candidate is removed, but future packets keep updating their own established fromNum entry.
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeATff7b72]Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeBTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffIncomingTa5d6ff"Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3fromNumTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`ambiguous noncanonical renumber installs identity into empty slot in memory`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3previousNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3fromNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3previousNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3previousNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPreviousTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3fromNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!incomingTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffIncomingTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffINCTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3previousNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3incoming Tff7b72= Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3fromNumTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffIncomingTa5d6ff"Tb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validPkTb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`ambiguous noncanonical renumber replaces telemetry placeholder without session lockout`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3previousNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff3T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3fromNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff4T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3previousNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3previousNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPreviousTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(
Te6edf3fromNum Tff7b72= Te6edf3fromNumTb4b4b4,
Te6edf3myNodeNum Tff7b72= T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3p Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4)Tb4b4b4,
Te6edf3defaultTime Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3fromNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!incomingTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffFirst IdentityTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffONETa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3fromNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!incomingTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdated IdentityTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffTWOTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3previousNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3incoming Tff7b72= Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3fromNumTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffUpdated IdentityTa5d6ff"Tb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validPkTb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3incomingTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 5. Local authoritative renumber
Tf0883e@Test
Tff7b72fun Td2a8ff`local authoritative renumber removes old same-key entries in memory upserts local no notification`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3localNum Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3otherNum Tff7b72= T79c0ff6T79c0ff0T79c0ff0T79c0ff0
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3localNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3otherNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3otherNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOld Same-KeyTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3localUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLocal NodeTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLCLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3localNumTb4b4b4, Te6edf3localUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3otherNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3localNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3localNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3localNodeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffLocal NodeTa5d6ff"Tb4b4b4, Te6edf3localNodeTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validPkTb4b4b4, Te6edf3localNodeTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 6. Genuine new node
Tf0883e@Test
Tff7b72fun Td2a8ff`genuine new node fires exactly one upsert and exactly one notification`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3newNodeNum Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3newPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3newUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!newnodeTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffNew NodeTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffNEWTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3newPkTb4b4b4,
Tb4b4b4)
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3captured Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3capturedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3newNodeNumTb4b4b4, Te6edf3newUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3newNodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffNew NodeTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3newPkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Strengthen: capture the dispatched Notification and verify payload + routing fields, not just the call count.
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3capturedTb4b4b4.Te6edf3sizeTb4b4b4)
Tff7b72val Te6edf3n Tff7b72= Te6edf3capturedTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3newNodeNumTb4b4b4, Te6edf3nTb4b4b4.Te6edf3idTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffNew NodeTa5d6ff"Tb4b4b4, Te6edf3nTb4b4b4.Te6edf3messageTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3NotificationTb4b4b4.Te6edf3CategoryTb4b4b4.Te6edf3NodeEventTb4b4b4, Te6edf3nTb4b4b4.Te6edf3categoryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffmeshtastic://meshtastic/nodes/Tffd700$Te6edf3newNodeNumTa5d6ff"Tb4b4b4, Te6edf3nTb4b4b4.Te6edf3deepLinkUriTb4b4b4)
Tb4b4b4}
T8b949e// 7. Invalid / malformed keys (null, empty, ERROR) are treated as NoMatch
Tf0883e@Test
Tff7b72fun Td2a8ff`invalid public keys skip identity matching and update normally`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3existingNum Tff7b72= T79c0ff7T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3existingPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3existingNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3existingNumTb4b4b4, Te6edf3existingPkTb4b4b4, Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
T8b949e// Three packets at a different number without a validated public-key correlation hint.
T8b949e// None of them should match the canonical node at 7000 or trigger stale cleanup.
Tff7b72val Te6edf3targetNum Tff7b72= T79c0ff8T79c0ff0T79c0ff0T79c0ff0
T8b949e// (a) null key
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3targetNumTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!aTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffATa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffATa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// (b) empty key
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3targetNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!bTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffBTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffBTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// (c) ERROR key
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3targetNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!cTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3NodeTb4b4b4.Te6edf3ERROR_BYTE_STRINGTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Canonical node untouched (no stale cleanup).
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3existingNumTff7b72]Tb4b4b4)
Tb4b4b4}
T8b949e// 8. Same number same key is a normal update, not a cross-number stale
Tf0883e@Test
Tff7b72fun Td2a8ff`same number same key updates normally without stale classification`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOriginalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3updatedUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!Tffd700${Te6edf3nodeNumTb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffUPDTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3updatedUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffUpdatedTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validPkTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 9. Packet-driven duplicate cleanup stays entirely in memory.
Tf0883e@Test
Tff7b72fun Td2a8ff`stale duplicate never deletes or upserts a repository row`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3newNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3newNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3newNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3oldNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffStale DupTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!oldTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStale PacketTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3staleUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 10. No notification for stale / conflict / local outcomes
Tf0883e@Test
Tff7b72fun Td2a8ff`notification never fires for stale conflict or local outcomes`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Stale: stale packet at old number with established canonical elsewhere.
Tff7b72val Te6edf3staleOld Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3staleCanonical Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3staleCanonicalTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3staleCanonicalTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3staleOldTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3staleOldTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3staleOldTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!sTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffSTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4, Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e// Conflict: different established identity at fromNum.
Tff7b72val Te6edf3conflictFrom Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3conflictOther Tff7b72= T79c0ff4T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3conflictPk Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3conflictOtherTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3conflictOtherTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOtherTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3conflictFromTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3conflictFromTb4b4b4, Te6edf3conflictPkTb4b4b4, Ta5d6ff"Ta5d6ffEstablishedTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3conflictFromTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!cTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4, Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e// Local: fromNum == myNodeNum, local-link authoritative update.
Tff7b72val Te6edf3localNum Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3localGhost Tff7b72= T79c0ff6T79c0ff0T79c0ff0T79c0ff0
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3localNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3localGhostTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3localGhostTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffGhostTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3localNumTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!lTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4, Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// No notification fired for any of the three outcomes.
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 11. byId consistency after stale removal β when stale and canonical share a user ID,
T8b949e// removing the stale node must not orphan the canonical node in the byId index.
Tf0883e@Test
Tff7b72fun Td2a8ff`byId index points to canonical survivor after stale removal shares user id`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3newNum Tff7b72= Te6edf3validPkTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3sharedUserId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3newNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3newNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3sharedUserIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCANTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3oldNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3oldNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3sharedUserIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3sharedUserIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validPkTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3staleUserTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3survivor Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3sharedUserIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3survivorTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3newNumTb4b4b4, Te6edf3survivorTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// ββ Additional identity reconciliation tests ββββββββββββββββββββββββββββββββ
T8b949e// 12. Malformed key lengths (1, 31, 33) are rejected
Tf0883e@Test
Tff7b72fun Td2a8ff`malformed key lengths 1 31 33 are rejected and skip identity matching`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3shortKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1Tb4b4b4) Tb4b4b4{ T79c0ff1 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3almostKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff1Tb4b4b4) Tb4b4b4{ T79c0ff2 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3longKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff3Tb4b4b4) Tb4b4b4{ T79c0ff3 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// All malformed keys should be treated as no-key (normal update path).
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3nodeNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!shortTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffSTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3shortKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72var Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3nodeNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!almostTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffATa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffATa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3almostKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3nodeNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!longTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3longKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Tb4b4b4}
T8b949e// 13. Invalid Node.publicKey falls back to valid User.public_key
Tf0883e@Test
Tff7b72fun Td2a8ff`invalid Node publicKey falls back to valid User public_key`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3validUserKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff4T79c0ff2 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3invalidNodeKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4) Tb4b4b4{ T79c0ff1 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4) T8b949e// Wrong size
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3nodeNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!testTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTestTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffTTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3validUserKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e// Manually set an invalid Node.publicKey
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3publicKey Tff7b72= Te6edf3invalidNodeKeyTb4b4b4) Tb4b4b4}
T8b949e// Public-key correlation should fall back to User.public_key.
Tff7b72val Te6edf3node Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Tff7b72val Te6edf3resolvedKey Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3resolveNodePublicKeyHintTb4b4b4(Te6edf3nodeTff7b72!!Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3validUserKeyTb4b4b4, Te6edf3resolvedKeyTb4b4b4)
Tb4b4b4}
T8b949e// 14. UNSET hardware with a different valid key is preserved (conflict)
Tf0883e@Test
Tff7b72fun Td2a8ff`UNSET hardware with different valid key is preserved as conflict`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3canonicalNum Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3conflictNum Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3canonicalKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff1T79c0ff0 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3conflictKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff2T79c0ff0 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// Canonical node with established identity
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4, Te6edf3canonicalKeyTb4b4b4, Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
T8b949e// Conflict node with UNSET hardware but different valid key
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3conflictNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3conflictNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!conflictTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffConflictTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCONTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3conflictKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3conflictKeyTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Packet at conflictNum with canonicalKey should be treated as conflict (preserve both)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3conflictNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!cTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e// Both nodes should still exist
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3conflictNumTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3canonicalKeyTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]Tff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3conflictKeyTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3conflictNumTff7b72]Tff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Tb4b4b4}
T8b949e// 15. Custom incomplete identity is preserved
Tf0883e@Test
Tff7b72fun Td2a8ff`custom incomplete identity is preserved over default incoming`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3customUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!customTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffMCNTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4, T8b949e// Incomplete hardware
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3customUserTb4b4b4) Tb4b4b4}
T8b949e// Incoming default user should not overwrite custom identity
Tff7b72val Te6edf3defaultUser Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic Tffd700${Te6edf3nodeNumTb4b4b4.Te6edf3toHexTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3nodeNumTb4b4b4.Te6edf3toHexTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3defaultUserTb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeNumTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMy Custom NameTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffMCNTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3userTb4b4b4.Te6edf3short_nameTb4b4b4)
Tb4b4b4}
T8b949e// 16. Three nodes sharing one user ID
Tf0883e@Test
Tff7b72fun Td2a8ff`three nodes sharing one user ID selects deterministic representative`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Tff7b72val Te6edf3node1 Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3node2 Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3node3 Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0
T8b949e// All three have the same user ID but different node numbers
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3node1Tb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node1Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3node2Tb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node2Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3node3Tb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node3Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN3Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN3Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// byId should point to node2 (non-placeholder, lowest among non-placeholders)
Tff7b72val Te6edf3representative Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3userIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3representativeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3node2Tb4b4b4, Te6edf3representativeTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// 17. Old-ID survivor restoration after put
Tf0883e@Test
Tff7b72fun Td2a8ff`old ID survivor restored after put changes user ID`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3oldUserId Tff7b72= Ta5d6ff"Ta5d6ff!oldTa5d6ff"
Tff7b72val Te6edf3newUserId Tff7b72= Ta5d6ff"Ta5d6ff!newTa5d6ff"
Tff7b72val Te6edf3survivorNum Tff7b72= T79c0ff5T79c0ff6T79c0ff7T79c0ff8
T8b949e// Two nodes with old user ID
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3nodeNumTb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3oldUserIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffOLDTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3survivorNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3survivorNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3oldUserIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffSurvivorTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSURTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Change nodeNum's user ID
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3newUserIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffNewTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffNEWTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// byId for oldUserId should now point to survivorNum
Tff7b72val Te6edf3survivor Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3oldUserIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3survivorTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3survivorNumTb4b4b4, Te6edf3survivorTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
T8b949e// byId for newUserId should point to nodeNum
Tff7b72val Te6edf3newNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3newUserIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3newNodeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3newNodeTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// 18. fromByNum insertion-order independence
Tf0883e@Test
Tff7b72fun Td2a8ff`fromByNum is independent of insertion order`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Tff7b72val Te6edf3node1 Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3node2 Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3nodes12 Tff7b72=
Te6edf3mapOfTb4b4b4(
Te6edf3node1 Te6edf3to
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node1Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3node2 Te6edf3to
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node2Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3nodes21 Tff7b72=
Te6edf3mapOfTb4b4b4(
Te6edf3node2 Te6edf3to
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node2Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3node1 Te6edf3to
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3node1Tb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3index12 Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3NodeIndexTb4b4b4.Te6edf3fromByNumTb4b4b4(Te6edf3nodes12Tb4b4b4)
Tff7b72val Te6edf3index21 Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3NodeIndexTb4b4b4.Te6edf3fromByNumTb4b4b4(Te6edf3nodes21Tb4b4b4)
T8b949e// Both should produce the same byId representative (node2, non-placeholder)
Te6edf3assertEqualsTb4b4b4(Te6edf3index12Tb4b4b4.Te6edf3byIdTff7b72[Te6edf3userIdTff7b72]Tff7b72!!Tb4b4b4.Te6edf3numTb4b4b4, Te6edf3index21Tb4b4b4.Te6edf3byIdTff7b72[Te6edf3userIdTff7b72]Tff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3node2Tb4b4b4, Te6edf3index12Tb4b4b4.Te6edf3byIdTff7b72[Te6edf3userIdTff7b72]Tff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// 19. Preferred canonical mapping after stale removal
Tf0883e@Test
Tff7b72fun Td2a8ff`preferred canonical remains mapped after stale removal`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Tff7b72val Te6edf3canonicalKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff4T79c0ff1 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3competingKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff4T79c0ff2 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3preferredNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3competingNum Tff7b72= T79c0ff5T79c0ff0T79c0ff0
Tff7b72val Te6edf3staleNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3competingNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3competingNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffFallback WinnerTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffFBKTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3competingKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3competingKeyTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3preferredNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3preferredNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffPreferredTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffPRETa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3staleNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3staleNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStaleTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3staleNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffStale ReplayTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffSTLTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3staleNumTff7b72]Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3competingNumTff7b72]Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3preferredNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3representative Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3userIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3representativeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3preferredNumTb4b4b4, Te6edf3representativeTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// 20. Accepted local node remains the byId representative
Tf0883e@Test
Tff7b72fun Td2a8ff`accepted local node remains byId representative`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Tff7b72val Te6edf3remoteNum Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3localNum Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3localNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3localNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3localNumTb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLocalTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLOCTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3remoteNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3remoteNumTb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemoteTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffREMTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(Te6edf3remoteNumTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3userIdTb4b4b4)Tff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3localNumTb4b4b4,
Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLocal UpdatedTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLOCTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3remoteNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3representative Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3userIdTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3representativeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3localNumTb4b4b4, Te6edf3representativeTff7b72!!Tb4b4b4.Te6edf3numTb4b4b4)
Tb4b4b4}
T8b949e// 21. Exact notification title, message, ID, category, and deep link
Tf0883e@Test
Tff7b72fun Td2a8ff`notification has exact title message ID category and deep link`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4
Tff7b72val Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!testTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffTest UserTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffTSTTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3userTb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{
Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(
Te6edf3NotificationTb4b4b4(
Te6edf3title Tff7b72= Ta5d6ff"Ta5d6ffNew node seen: TSTTa5d6ff"Tb4b4b4,
Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffTest UserTa5d6ff"Tb4b4b4,
Te6edf3category Tff7b72= Te6edf3NotificationTb4b4b4.Te6edf3CategoryTb4b4b4.Te6edf3NodeEventTb4b4b4,
Te6edf3id Tff7b72= Te6edf3nodeNumTb4b4b4,
Te6edf3deepLinkUri Tff7b72= Ta5d6ff"Ta5d6ffmeshtastic://meshtastic/nodes/Tffd700$Te6edf3nodeNumTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// 22. Incoming at the canonical num reconciles in memory only and preserves its placeholder history.
Tf0883e@Test
Tff7b72fun Td2a8ff`incoming canonical replaces own placeholder and removes noncanonical in memory only`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3canonicalKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff7Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff7T79c0ff7T79c0ff7T79c0ff7Tb4b4b4)
T8b949e// Pre-existing entry at the noncanonical num carries the same key.
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3staleNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3staleNumTb4b4b4, Te6edf3canonicalKeyTb4b4b4, Ta5d6ff"Ta5d6ffGhostTa5d6ff"Tb4b4b4) Tb4b4b4}
T8b949e// Placeholder at the canonical num so the incoming slot is "open" for reconciliation.
Tff7b72val Te6edf3placeholderId Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3canonicalNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4) Tb4b4b4{
Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3canonicalNumTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3placeholderIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic Tffd700${Te6edf3placeholderIdTb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3placeholderIdTb4b4b4.Te6edf3takeLastTb4b4b4(T79c0ff4Tb4b4b4)Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4,
Te6edf3position Tff7b72= Te6edf3ProtoPositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff2T79c0ff2T79c0ff2Tb4b4b4)Tb4b4b4,
Te6edf3channel Tff7b72= T79c0ff4Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3canonicalNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!canonicalTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCANTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3staleNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3canonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3canonicalTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffCanonicalTa5d6ff"Tb4b4b4, Te6edf3canonicalTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3canonicalKeyTb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff1T79c0ff1Tb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff2T79c0ff2Tb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4Tb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3channelTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 23. Incoming canonical absent from index entirely creates from packet, removes noncanonical, no notify.
Tf0883e@Test
Tff7b72fun Td2a8ff`incoming canonical absent from index creates from packet removes noncanonical no notify`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3canonicalKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff1T79c0ff7Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3staleNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff8T79c0ff8T79c0ff8T79c0ff8Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3staleNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3staleNumTb4b4b4, Te6edf3canonicalKeyTb4b4b4, Ta5d6ff"Ta5d6ffStale NoncanonicalTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3canonicalNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!freshTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffFresh CanonicalTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffFRSTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3staleNumTff7b72]Tb4b4b4)
Tff7b72val Te6edf3canonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3canonicalTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffFresh CanonicalTa5d6ff"Tb4b4b4, Te6edf3canonicalTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3canonicalKeyTb4b4b4, Te6edf3canonicalTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 24. Neither num canonical preserves both with no side effects.
Tf0883e@Test
Tff7b72fun Td2a8ff`neither num canonical preserves both no persistence no notification`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff2T79c0ff3Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// Pick two nums guaranteed distinct from the canonical num and from each other.
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3keyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3a Tff7b72= Tff7b72if Tb4b4b4(Te6edf3canonicalNum Tff7b72!Tff7b72= T79c0ff1T79c0ff1T79c0ff1T79c0ff1Tb4b4b4) T79c0ff1T79c0ff1T79c0ff1T79c0ff1 Tff7b72else T79c0ff1T79c0ff1T79c0ff1T79c0ff2
Tff7b72val Te6edf3b Tff7b72= Tff7b72if Tb4b4b4(Te6edf3canonicalNum Tff7b72!Tff7b72= T79c0ff2T79c0ff2T79c0ff2T79c0ff2Tb4b4b4) T79c0ff2T79c0ff2T79c0ff2T79c0ff2 Tff7b72else T79c0ff2T79c0ff2T79c0ff2T79c0ff3
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3aTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3aTb4b4b4, Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffAlphaTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3bTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3bTb4b4b4, Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffBravoTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3aTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!aTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffAlpha PacketTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffALATa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3keyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Both are preserved; the incoming packet still updates its own established same-key slot in memory.
Tff7b72val Te6edf3alpha Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3aTff7b72]
Tff7b72val Te6edf3bravo Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3bTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3alphaTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3bravoTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffAlpha PacketTa5d6ff"Tb4b4b4, Te6edf3alphaTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffBravoTa5d6ff"Tb4b4b4, Te6edf3bravoTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 25. Different established valid key at the canonical num is preserved as a conflict.
Tf0883e@Test
Tff7b72fun Td2a8ff`different established key at canonical num preserved as conflict`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3canonicalKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff3T79c0ff1Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3establishedKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff3T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3noncanonicalNum Tff7b72= Te6edf3canonicalKeyTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff3T79c0ff3T79c0ff3T79c0ff3Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3noncanonicalNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3noncanonicalNumTb4b4b4, Te6edf3canonicalKeyTb4b4b4, Ta5d6ff"Ta5d6ffNoncanonical HolderTa5d6ff"Tb4b4b4) Tb4b4b4}
T8b949e// Canonical num is occupied by a node with a DIFFERENT valid key.
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3canonicalNumTb4b4b4, Te6edf3establishedKeyTb4b4b4, Ta5d6ff"Ta5d6ffEstablishedTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3canonicalNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!cTa5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffClaimantTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffCLMTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3canonicalKeyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Conflict: both preserved with their original keys; no persistence, no notification.
Tff7b72val Te6edf3atCanonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3canonicalNumTff7b72]
Tff7b72val Te6edf3atNoncanonical Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3noncanonicalNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3atCanonicalTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3atNoncanonicalTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3establishedKeyTb4b4b4, Te6edf3atCanonicalTff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffEstablishedTa5d6ff"Tb4b4b4, Te6edf3atCanonicalTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3canonicalKeyTb4b4b4, Te6edf3atNoncanonicalTff7b72!!Tb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// 26. Local classification and the node index share one atomic CAS state.
Tf0883e@Test
Tff7b72fun Td2a8ff`local number change during User reduction retries classification atomically`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff3T79c0ff7Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3previousNum Tff7b72= Te6edf3keyTb4b4b4.Te6edf3canonicalNumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3newLocalNum Tff7b72= Te6edf3keyTb4b4b4.Te6edf3noncanonicalNumTb4b4b4(T79c0ff9T79c0ff9T79c0ff9T79c0ff9Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3previousNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3previousNumTb4b4b4, Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffPreviousTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3enableDbWritesTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3receivedUserReductionHook Tff7b72= Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3receivedUserReductionHook Tff7b72= Tff7b72null
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3newLocalNumTb4b4b4)
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3newLocalNumTb4b4b4,
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!local9999Ta5d6ff"Tb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffCurrent LocalTa5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLOCTa5d6ff"Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3keyTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3testScopeTb4b4b4.Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3previousNumTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffCurrent LocalTa5d6ff"Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3newLocalNumTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3newLocalNumTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3valueTb4b4b4)
Te6edf3verifyNoRepositoryDeletionTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`reverse ID and public-key indexes stay consistent across put replacement and remove`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3keyA Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff1T79c0ff1 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3keyB Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff1T79c0ff2 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3sharedId Tff7b72= Ta5d6ff"Ta5d6ff!sharedTa5d6ff"
Tff7b72val Te6edf3replacementId Tff7b72= Ta5d6ff"Ta5d6ff!replacementTa5d6ff"
Tff7b72var Te6edf3index Tff7b72= Te6edf3NodeManagerImplTb4b4b4.Te6edf3NodeIndexTb4b4b4(Tb4b4b4)
Te6edf3index Tff7b72= Te6edf3indexTb4b4b4.Te6edf3putTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3makeKnownNodeTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3keyATb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3makeKnownNodeTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3keyATb4b4b4)Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3sharedIdTb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3index Tff7b72= Te6edf3indexTb4b4b4.Te6edf3putTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3makeKnownNodeTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3keyATb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3makeKnownNodeTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3keyATb4b4b4)Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3sharedIdTb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByIdTff7b72[Te6edf3sharedIdTff7b72]Tb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyATff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3sharedIdTff7b72]Tff7b72?.Te6edf3numTb4b4b4)
Tff7b72val Te6edf3replacement Tff7b72= Te6edf3makeKnownNodeTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3keyBTb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3makeKnownNodeTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3keyBTb4b4b4)Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3replacementIdTb4b4b4)Tb4b4b4)
Te6edf3index Tff7b72= Te6edf3indexTb4b4b4.Te6edf3putTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3replacementTb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByIdTff7b72[Te6edf3sharedIdTff7b72]Tb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByIdTff7b72[Te6edf3replacementIdTff7b72]Tb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyATff7b72]Tb4b4b4)
Te6edf3assertEqualsTff7b72<Te6edf3SetTff7b72<Tffa657IntTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Te6edf3setOfTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyBTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3sharedIdTff7b72]Tff7b72?.Te6edf3numTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3replacementIdTff7b72]Tff7b72?.Te6edf3numTb4b4b4)
Te6edf3index Tff7b72= Te6edf3indexTb4b4b4.Te6edf3removeTb4b4b4(T79c0ff2Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3indexTb4b4b4.Te6edf3candidateNumsByIdTff7b72[Te6edf3sharedIdTff7b72]Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3indexTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyATff7b72]Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3sharedIdTff7b72]Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3replacementIdTff7b72]Tff7b72?.Te6edf3numTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Te6edf3IntTb4b4b4.Td2a8fftoHexTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Tff7b72thisTb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(T79c0ff8Tb4b4b4, Ta5d6ff'0'Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffuserWithKeyTb4b4b4(Te6edf3keyTb4b4b4: Te6edf3ByteStringTb4b4b4, Te6edf3longNameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3shortNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3User Tff7b72= Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3keyTb4b4b4.Te6edf3hexTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3long_name Tff7b72= Te6edf3longNameTb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3shortNameTb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TLORA_V2Tb4b4b4,
Te6edf3public_key Tff7b72= Te6edf3keyTb4b4b4,
Tb4b4b4)
T8b949e// ---------- Retired-node notification behavior ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`applyTrustedIdentityMigrations cancels notifications for each retired number`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
T8b949e// Setup: create nodes with distinct identities
Tff7b72val Te6edf3num1 Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff0T79c0ff5T79c0ff8T79c0ff8T79c0ff5T79c0ff7T79c0ff8
Tff7b72val Te6edf3num2 Tff7b72= T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1T79c0ff1
Tff7b72val Te6edf3key1 Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x01 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key2 Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x02 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3num1Tb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3key1Tb4b4b4, Ta5d6ff"Ta5d6ffNode1Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffN1Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3num2Tb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3key2Tb4b4b4, Ta5d6ff"Ta5d6ffNode2Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffN2Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Act
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3num1Tb4b4b4, Te6edf3num2Tb4b4b4)Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Assert
Te6edf3verifyTb4b4b4(Te6edf3VerifyModeTb4b4b4.Te6edf3atLeastTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3cancelTb4b4b4(Te6edf3num1Tb4b4b4) Tb4b4b4}
Te6edf3verifyTb4b4b4(Te6edf3VerifyModeTb4b4b4.Te6edf3atLeastTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3cancelTb4b4b4(Te6edf3num2Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`trusted migration commits retirement before notification cancellation`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff3T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x12 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffRetiringTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRTTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3nodePresentAtCancellationTb4b4b4: Tffa657Boolean? Tff7b72= Tff7b72null
Te6edf3every Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3cancelTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3nodePresentAtCancellation Tff7b72= Te6edf3num Tff7b72in Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNum
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Tff7b72falseTb4b4b4, Te6edf3nodePresentAtCancellationTb4b4b4, Ta5d6ff"Ta5d6ffretirement must commit before cancellation side effectsTa5d6ff"Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4)
Te6edf3verifyTb4b4b4(Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3cancelTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`notification skipped when number retired between select and dispatch`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff4T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x03 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3titleStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseTitle Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3notificationTitleFormatter Tff7b72= Tb4b4b4{ Te6edf3shortName Tff7b72-Tff7b72>
Te6edf3titleStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseTitleTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Ta5d6ff"Ta5d6ffNew node seen: Tffd700$Te6edf3shortNameTa5d6ff"
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffNewNodeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNNTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3titleStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3releaseTitleTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3dispatchedTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3message Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffNewNodeTa5d6ff" Tb4b4b4}Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`notification skipped when number becomes local node before dispatch`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x04 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3titleStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseTitle Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3notificationTitleFormatter Tff7b72= Tb4b4b4{ Te6edf3shortName Tff7b72-Tff7b72>
Te6edf3titleStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseTitleTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Ta5d6ff"Ta5d6ffNew node seen: Tffd700$Te6edf3shortNameTa5d6ff"
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffNewNodeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNNTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3titleStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3numTb4b4b4)
Te6edf3releaseTitleTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3dispatchedTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3message Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffNewNodeTa5d6ff" Tb4b4b4}Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`notification skipped when identity changed before dispatch`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff6T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key1 Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x05 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key2 Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x06 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3titleStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releaseTitle Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3notificationTitleFormatter Tff7b72= Tb4b4b4{ Te6edf3shortName Tff7b72-Tff7b72>
Te6edf3titleStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releaseTitleTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Ta5d6ff"Ta5d6ffNew node seen: Tffd700$Te6edf3shortNameTa5d6ff"
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3key1Tb4b4b4, Ta5d6ff"Ta5d6ffFirstTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffF1Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3titleStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3numTb4b4b4, Te6edf3key2Tb4b4b4, Ta5d6ff"Ta5d6ffOtherTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3releaseTitleTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3node Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffOtherTa5d6ff"Tb4b4b4, Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3dispatchedTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3message Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffFirstTa5d6ff" Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffnotification for 'First' must be suppressedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`retired number with same key suppressed even without canonical key candidate`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x07 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// Create node at oldNum with key, then retire it via migration
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffOldNodeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffONTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3oldNumTb4b4b4)Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3replayDispatches Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3replayDispatchesTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
T8b949e// Now replay: the canonical number (crc32(key)) has NOT appeared yet
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffReplayTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRPTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// The replayed node should NOT appear in nodeDBbyNodeNum at oldNum
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3replayDispatchesTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3oldNum Tb4b4b4}Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`keyless retired number rejects an unrepresented valid key for the session`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff7T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3incomingKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x17 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(
Te6edf3numTb4b4b4,
Te6edf3userWithKeyTb4b4b4(Te6edf3incomingKeyTb4b4b4, Ta5d6ff"Ta5d6ffUntrusted ReuseTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffURTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`retired number with different valid key can be legitimately reused`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff8T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3oldKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x08 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3newKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x09 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// Create and retire with old key
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3oldKeyTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffODTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Capture dispatches to verify the reuse notification specifically,
T8b949e// not the initial sighting from the setup above.
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
T8b949e// Replay with different key β should be allowed as legitimate reuse
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3newKeyTb4b4b4, Ta5d6ff"Ta5d6ffNewTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNWTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3newNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3newNodeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffNewTa5d6ff"Tb4b4b4, Te6edf3newNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(
T79c0ff1Tb4b4b4,
Te6edf3dispatchedTb4b4b4.Te6edf3count Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3message Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffNewTa5d6ff" Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffexactly one notification for the reuse at Tffd700$Te6edf3numTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`legitimate reuse clears prior retirement hint before a later keyless retirement`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff8T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3oldKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x18 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3reuseKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x19 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3untrustedKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x1a Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3oldKeyTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffODTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3reuseKeyTb4b4b4, Ta5d6ff"Ta5d6ffReuseTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRETa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffReuseTa5d6ff"Tb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3removeByNodenumTb4b4b4(Te6edf3numTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3untrustedKeyTb4b4b4, Ta5d6ff"Ta5d6ffUntrustedTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffUNTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffan absent trusted migration must not retain an obsolete hintTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`public key diagnostics use a one-way fingerprint`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fingerprint Tff7b72= Te6edf3publicKeyLogFingerprintTb4b4b4(Te6edf3keyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3keyTb4b4b4.Te6edf3sha256Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3hexTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeTb4b4b4(T79c0ff8Tb4b4b4)Tb4b4b4, Te6edf3fingerprintTb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3fingerprint Tff7b72=Tff7b72= Te6edf3keyTb4b4b4.Te6edf3hexTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeTb4b4b4(T79c0ff8Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffdiagnostics must not expose a raw public-key prefixTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`complete 2_7 to 2_8 renumbering produces no old number notification`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3oldNum Tff7b72= Tff7b72-T79c0ff2T79c0ff9T79c0ff7T79c0ff1T79c0ff1T79c0ff4T79c0ff1T79c0ff9T79c0ff1
Tff7b72val Te6edf3newNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff0T79c0ff5T79c0ff8T79c0ff8T79c0ff5T79c0ff7T79c0ff8
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x0a Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
T8b949e// Simulate: old 2.7 local node identity
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3oldNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffOldLocalTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffOLTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Trusted migration retires old number, canonical identity moves to newNum
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3newNumTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3newNumTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffNewLocalTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNLTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3oldNumTb4b4b4)Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Early replay of old number User packet β should be suppressed
Tff7b72val Te6edf3dispatchedBefore Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedBeforeTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffReplayTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRPTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// The old number should NOT be in nodeDB
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4)
T8b949e// No notification should have been dispatched for the old number replay
T8b949e// (the notification dispatch was captured; check none is for oldNum)
Tff7b72val Te6edf3oldNumNotifications Tff7b72= Te6edf3dispatchedBeforeTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3oldNum Tb4b4b4}
Te6edf3assertTrueTb4b4b4(Te6edf3oldNumNotificationsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffNo notification should be dispatched for retired oldNumTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`genuine remote first sighting emits exactly one notification`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3key Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x0b Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff0T79c0ff5T79c0ff8T79c0ff8T79c0ff5T79c0ff7T79c0ff8Tb4b4b4)
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3keyTb4b4b4, Ta5d6ff"Ta5d6ffGenuine RemoteTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffGRTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3numNotifications Tff7b72= Te6edf3dispatchedTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3numNotificationsTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffExactly one notification for genuine new nodeTa5d6ff"Tb4b4b4)
Tb4b4b4}
T8b949e// ββ Phase 8 β session-safe node cache loading & trusted-hint survival βββββββββ
Tf0883e@Test
Tff7b72fun Td2a8ff`direct snapshot returns only the newly selected DB after a currentDb switch`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
T8b949e// Simulate the race that motivated Phase 6: the process-wide nodeDBbyNum StateFlow (a stateIn cache) still
T8b949e// holds the PREVIOUS database's node map right after a currentDb switch, but the direct snapshot read
T8b949e// returns
T8b949e// only the newly selected DB's nodes.
Tff7b72val Te6edf3staleNum Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff1
Tff7b72val Te6edf3freshNum Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff2
Tff7b72val Te6edf3staleNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3staleNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOld DBTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3freshNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3freshNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffNew DBTa5d6ff"Tb4b4b4)
T8b949e// Stale StateFlow cache (old DB still reflected) vs. the direct snapshot (new DB only).
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3mapOfTb4b4b4(Te6edf3staleNum Te6edf3to Te6edf3staleNodeTb4b4b4)Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3mapOfTb4b4b4(Te6edf3freshNum Te6edf3to Te6edf3freshNodeTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// The cache loader consumed the direct snapshot, so staleNum never entered the live index and freshNum did.
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3staleNumTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffstale-DB row must not leak into the live indexTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3loaded Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3freshNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3loadedTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffNew DBTa5d6ff"Tb4b4b4, Te6edf3loadedTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`loadCachedNodeDB result is discarded when clear starts a new session mid-load`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
T8b949e// Pause the load after the snapshot read so we can race a clear() against it before commit. We use the
T8b949e// repository snapshot call as the suspension point.
Tff7b72val Te6edf3oldNum Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff3
Tff7b72val Te6edf3oldNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3oldNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3snapshotRead Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3snapshotRelease Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3snapshotReadTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3mapOfTb4b4b4(Te6edf3oldNum Te6edf3to Te6edf3oldNodeTb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3snapshotReadTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// While the load is paused after its read, a new session starts. This must discard the old snapshot.
Te6edf3nodeManagerTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
T8b949e// Add a node AFTER clear so we can verify the discarded load did not wipe it on commit.
Tff7b72val Te6edf3liveNum Tff7b72= T79c0ff4T79c0ff0T79c0ff0T79c0ff4
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3liveNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3liveNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffLive After ClearTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3snapshotReleaseTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// The old-DB snapshot was discarded completely: oldNum is absent, liveNum survives.
Te6edf3assertNullTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3oldNumTff7b72]Tb4b4b4,
Ta5d6ff"Ta5d6ffstale snapshot from the previous session must be discardedTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3live Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3liveNumTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3liveTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffLive After ClearTa5d6ff"Tb4b4b4, Te6edf3liveTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`live node update during the load window is not overwritten by the snapshot`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff5
Tff7b72val Te6edf3staleSnapshotNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3numTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffStale SnapshotTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3liveUpdatedNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3numTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffLive UpdatedTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3snapshotRead Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3snapshotRelease Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3snapshotReadTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3mapOfTb4b4b4(Te6edf3num Te6edf3to Te6edf3staleSnapshotNodeTb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3snapshotReadTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// A live update arrives between the snapshot capture and its commit. The live value (with fresher fields)
T8b949e// must win over the snapshot row at the same num.
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4{ Te6edf3liveUpdatedNode Tb4b4b4}
Te6edf3snapshotReleaseTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffLive UpdatedTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff0T79c0ff0Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3lastHeardTb4b4b4, Ta5d6ff"Ta5d6fflive mutation field must not be overwritten by the snapshotTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`trusted migration during the load window retires absent number and suppresses replay`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff6T79c0ff0T79c0ff0T79c0ff6
Tff7b72val Te6edf3snapshotNode Tff7b72= Te6edf3makeKnownNodeTb4b4b4(Te6edf3numTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPre-migrationTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3snapshotRead Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3snapshotRelease Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3snapshotReadTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3mapOfTb4b4b4(Te6edf3num Te6edf3to Te6edf3snapshotNodeTb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3snapshotReadTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// Trusted migration runs while the load is suspended. The migrated number is absent at this point, so its
T8b949e// hint is captured as "absent" β but the retirement must still survive and suppress a stale replay even
T8b949e// after
T8b949e// the load commits.
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4,
Ta5d6ff"Ta5d6ffretired number absent at migration time stays retired post-loadTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// Stale same-key replay still suppressed, no notification dispatched for the retired old number.
Tff7b72val Te6edf3dispatched Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3dispatchedTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffReplayTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRPTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3dispatchedTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffno notification for retired-number replayTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`persisted local node fills null identity when live nodes change during load`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3persistedNum Tff7b72= T79c0ff7T79c0ff0T79c0ff0T79c0ff7
Tff7b72val Te6edf3liveNum Tff7b72= T79c0ff8T79c0ff0T79c0ff0T79c0ff8
Tff7b72val Te6edf3liveKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ Tb4b4b4(Tffa657it Tff7b72+ T79c0ff9T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3persistedInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3persistedNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3snapshotRead Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3snapshotRelease Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3snapshotReadTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3mapOfTb4b4b4(Te6edf3persistedNum Te6edf3to Te6edf3makeKnownNodeTb4b4b4(Te6edf3persistedNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPersistedTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3persistedInfoTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3snapshotReadTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeTb4b4b4(Te6edf3liveNumTb4b4b4) Tb4b4b4{ Te6edf3makeKnownNodeTb4b4b4(Te6edf3liveNumTb4b4b4, Te6edf3liveKeyTb4b4b4, Ta5d6ff"Ta5d6ffLiveTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3snapshotReleaseTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(
Te6edf3persistedNumTb4b4b4,
Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3valueTb4b4b4,
Ta5d6ff"Ta5d6ffthe merge path must restore persisted local identityTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3liveNumTff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffthe concurrent live update must also surviveTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`local-node identity learned during the load window survives snapshot commit`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3persistedNum Tff7b72= T79c0ff7T79c0ff0T79c0ff0T79c0ff7
Tff7b72val Te6edf3persistedInfo Tff7b72=
Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3persistedNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff0LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3liveLearnedNum Tff7b72= T79c0ff8T79c0ff0T79c0ff0T79c0ff8
Tff7b72val Te6edf3snapshotRead Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3snapshotRelease Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3snapshotReadTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// Persisted DB still references the old persistedNum row.
Te6edf3mapOfTb4b4b4(Te6edf3persistedNum Te6edf3to Te6edf3makeKnownNodeTb4b4b4(Te6edf3persistedNumTb4b4b4, Te6edf3validPkTb4b4b4, Ta5d6ff"Ta5d6ffPersistedTa5d6ff"Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3persistedInfoTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3loadCachedNodeDBTb4b4b4(Tb4b4b4)
Te6edf3snapshotReadTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// The live session has learned a DIFFERENT local node number during the load window. The persisted value
T8b949e// must NOT clobber it on commit.
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3liveLearnedNumTb4b4b4)
Te6edf3snapshotReleaseTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(
Te6edf3liveLearnedNumTb4b4b4,
Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3valueTb4b4b4,
Ta5d6ff"Ta5d6fflive local-node info wins over persisted snapshotTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`repeated trusted migration preserves original hint and still permits distinct-key reuse`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff9
Tff7b72val Te6edf3oldKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x21 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3reuseKey Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff0Te6edf3x22 Tb4b4b4}Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3oldKeyTb4b4b4, Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffODTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Apply the same migration twice. The original hint must survive the second pass because the node is
T8b949e// already
T8b949e// retired and absent; the prior hint is preserved rather than dropped.
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3applyTrustedIdentityMigrationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Same-key replay still suppressed after the repeated migration.
Tff7b72val Te6edf3suppressedDispatches Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3suppressedDispatchesTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3oldKeyTb4b4b4, Ta5d6ff"Ta5d6ffReplayTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRPTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertNullTb4b4b4(
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]Tb4b4b4,
Ta5d6ff"Ta5d6ffsame-key replay after repeated migration must stay suppressedTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3suppressedDispatchesTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tb4b4b4}Tb4b4b4)
T8b949e// Distinct valid unrepresented key is still accepted as a legitimate reuse, clearing retirement + hint and
T8b949e// emitting exactly one replacement notification.
Tff7b72val Te6edf3reuseDispatches Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3NotificationTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(Te6edf3captureTb4b4b4(Te6edf3reuseDispatchesTb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3numTb4b4b4, Te6edf3userWithKeyTb4b4b4(Te6edf3reuseKeyTb4b4b4, Ta5d6ff"Ta5d6ffReplacementTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNPTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3manuallyVerified Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3reused Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3numTff7b72]
Te6edf3assertNotNullTb4b4b4(Te6edf3reusedTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffReplacementTa5d6ff"Tb4b4b4, Te6edf3reusedTff7b72!!Tb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3reuseKeyTb4b4b4, Te6edf3reusedTb4b4b4.Te6edf3publicKeyTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3reuseDispatchesTb4b4b4.Te6edf3count Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3num Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3message Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffReplacementTa5d6ff" Tb4b4b4}Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.76s